/*
 * @(#)BMPFile.java     14/08/02
 * 
 * Author             Rick Wilson
 * Copyright (c) 2005 Rick Wilson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of 'Taareekah' nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */



/*Source code copied from the ImageJ project
* http://imagej.nih.gov/ij/
*
* "ImageJ is being developed at the National Institutes of Health by an
* employee of the Federal Government in the course of his official duties.
* Pursuant to Title 17, Section 105 of the United States Code, this software
* is not subject to copyright protection and is in the public domain. ImageJ
* is an experimental system. NIH assumes no responsibility whatsoever for its
* use by other parties, and makes no guarantees, expressed or implied, about
* its quality, reliability, or any other characteristic."
* http://imagej.nih.gov/ij/disclaimer.html
*
* http://imagej.nih.gov/ij/source/ij/plugin/BMP_Writer.java */
package util;

//~--- JDK imports ------------------------------------------------------------

import java.awt.*;

import java.io.*;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.JMException;

/**
 * Creates and saves data as a bitmap file.
 * @author rick
 */
public class BMPFile extends Component {

    // --- Private constants

    /** Field description */
    private final static int	BITMAPFILEHEADER_SIZE	= 14;

    /** Field description */
    private final static int	BITMAPINFOHEADER_SIZE	= 40;

    // --- Private variable declaration
    // --- Bitmap file header

    /** Field description */
    private byte	bitmapFileHeader[]	= new byte[14];

    /** Field description */
    private byte	bfType[]	= { (byte) 'B', (byte) 'M' };

    /** Field description */
    private int	bfSize	= 0;

    /** Field description */
    private int	bfReserved1	= 0;

    /** Field description */
    private int	bfReserved2	= 0;

    /** Field description */
    private int	bfOffBits	= BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE;

    // --- Bitmap info header

    /** Field description */
    private byte	bitmapInfoHeader[]	= new byte[40];

    /** Field description */
    private int	biSize	= BITMAPINFOHEADER_SIZE;

    /** Field description */
    private int	biWidth	= 0;

    /** Field description */
    private int	biHeight	= 0;

    /** Field description */
    private int	biPlanes	= 1;

    /** Field description */
    private int	biBitCount	= 24;

    /** Field description */
    private int	biCompression	= 0;

    /** Field description */
    private int	biSizeImage	= 0x030000;

    /** Field description */
    private int	biXPelsPerMeter	= 0x0;

    /** Field description */
    private int	biYPelsPerMeter	= 0x0;

    /** Field description */
    private int	biClrUsed	= 0;

    /** Field description */
    private int	biClrImportant	= 0;

    // --- Bitmap raw data

    /** Field description */
    private int	bitmap[];

    // --- File section

    /** Field description */
    private FileOutputStream	fo;

    // --- Default constructor

    /**
     *
     */
    public BMPFile() {}

    /**
     *
     * @param x_size
     * @param y_size
     * @param shader
     * @return
     * @throws Exception
     */
    public static int[] createBMP(int x_size, int y_size, BitmapPixelShader shader) throws Exception {
	int[]	buffer	= new int[x_size * y_size];

	for (int x = 0; x < x_size; x++) {
	    for (int y = 0; y < y_size; y++) {

		// Convert map points to bitmap array index
		int	index	= (x + (x_size - 1 - y) * x_size);

		buffer[index]	= BMPFile.rgbToIntArray(shader.getColorAtPoint(x, y));
	    }
	}

	return buffer;
    }

    /**
     *
     * @param filename
     * @param x_size
     * @param y_size
     * @param shader
     * @return
     * @throws Exception
     */
    public static boolean saveBMP(String filename, int x_size, int y_size, BitmapPixelShader shader) throws Exception {
	int[]	buffer	= new int[x_size * y_size];

	for (int x = 0; x < x_size; x++) {
	    for (int y = 0; y < y_size; y++) {

		// Convert map points to bitmap array index
		int	index	= (x + (x_size - 1 - y) * x_size);

		buffer[index]	= BMPFile.rgbToIntArray(shader.getColorAtPoint(x, y));
	    }
	}

	BMPFile	bmp	= new BMPFile();

	bmp.saveBitmap(filename, buffer, x_size, y_size);
	Logger.getLogger(BMPFile.class.getName()).log(Level.INFO, "Saved BMP {0}", filename);

	return true;
    }

    /**
     *
     * @param rgb
     * @return
     * @throws Exception
     */
    public static int rgbToIntArray(int[] rgb) throws Exception {
	return rgbToInt(rgb[0], rgb[1], rgb[2]);
    }

    /**
     *
     * @param r
     * @param g
     * @param b
     * @return
     * @throws Exception
     */
    public static int rgbToInt(int r, int g, int b) throws Exception {
	if ((r < 0) || (g < 0) || (b < 0)) {
	    Logger.getLogger(BMPFile.class.getName()).log(Level.WARNING, "RGB invalid {0},{1},{2}", new Object[] { r, g,
		    b });

	    throw new JMException("Negative color value");
	}

	if ((r > 255) || (g > 255) || (b > 255)) {
	    Logger.getLogger(BMPFile.class.getName()).log(Level.WARNING, "RGB invalid {0},{1},{2}", new Object[] { r, g,
		    b });

	    throw new JMException("To high color value");
	}

	return ((r << 16) | (g << 8) | b);
    }

    /**
     *
     * @param r
     * @param g
     * @param b
     * @return
     */
    public static int rgbToInt(byte r, byte g, byte b) {
	int	red	= (int) r;

	if (r < 0) {
	    red	= r + 256;
	}

	red	= red << 16;

	int	green	= (int) g;

	if (g < 0) {
	    green	= g + 256;
	}

	green	= green << 8;

	int	blue	= (int) b;

	if (b < 0) {
	    blue	= b + 256;
	}

	int	ret	= (red | green | blue);

	return ret;
    }

    /**
     *
     * @param parFilename
     * @param imagePix
     * @param parWidth
     * @param parHeight
     * @throws Exception
     */
    public void saveBitmap(String parFilename, int[] imagePix, int parWidth, int parHeight) throws Exception {
	fo	= new FileOutputStream(parFilename);
	save(imagePix, parWidth, parHeight);
	fo.close();
    }

    /*
     * The saveMethod is the main method of the process. This method
     * will call the convertImage method to convert the memory image to
     * a byte array; method writeBitmapFileHeader creates and writes
     * the bitmap file header; writeBitmapInfoHeader creates the
     * information header; and writeBitmap writes the image.
     *
     */

    /**
     * Method description
     *
     *
     * @param imagePix
     * @param parWidth
     * @param parHeight
     *
     * @throws Exception
     */
    private void save(int[] imagePix, int parWidth, int parHeight) throws Exception {
	convertImage(imagePix, parWidth, parHeight);
	writeBitmapFileHeader();
	writeBitmapInfoHeader();
	writeBitmap();
    }

    /*
     * convertImage converts the memory image to the bitmap format (BRG).
     * It also computes some information for the bitmap info header.
     *
     */

    /**
     * Method description
     *
     *
     * @param imagePix
     * @param parWidth
     * @param parHeight
     *
     * @return
     */
    private boolean convertImage(int[] imagePix, int parWidth, int parHeight) {
	int	pad;

	bitmap	= imagePix;

	////PixelGrabber pg = new PixelGrabber (parImage, 0, 0, parWidth, parHeight,
	////                            bitmap, 0, parWidth);
	// try {
	// pg.grabPixels ();
	// }
	// catch (InterruptedException e) {
	// e.printStackTrace ();
	// return (false);
	// }
	pad	= (4 - ((parWidth * 3) % 4)) * parHeight;

	if (4 - ((parWidth * 3) % 4) == 4) {
	    pad	= 0;
	}

	biSizeImage	= ((parWidth * parHeight) * 3) + pad;
	bfSize		= biSizeImage + BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE;
	biWidth		= parWidth;
	biHeight	= parHeight;

	return (true);
    }

    /*
     * writeBitmap converts the image returned from the pixel grabber to
     * the format required. Remember: scan lines are inverted in
     * a bitmap file!
     *
     * Each scan line must be padded to an even 4-byte boundary.
     */

    /**
     * Method description
     *
     *
     * @throws Exception
     */
    private void writeBitmap() throws Exception {
	int	size;
	int	value;
	int	j;
	int	i;
	int	rowCount;
	int	rowIndex;
	int	lastRowIndex;
	int	pad;
	int	padCount;
	byte	rgb[]	= new byte[3];

	size	= (biWidth * biHeight) - 1;
	pad	= 4 - ((biWidth * 3) % 4);

	// The following bug correction will cause the bitmap to be unreadable by
	// GIMP.  It must be there for the bitmap to be readable by most other
	// graphics packages.
	if (pad == 4) {		// <==== Bug correction
	    pad	= 0;
	}			// <==== Bug correction

	rowCount	= 1;
	padCount	= 0;
	rowIndex	= size - biWidth;
	lastRowIndex	= rowIndex;

	// try {
	// The following three lines of code are a correction supplied
	// by Alin Arsu, Feb 2003.  The original code set the top-right
	// pixel in the image to black, and also shifted the bottom row
	// of the image by one pixel.
	// The original code was the following two lines:
	// for (j = 0; j < size; j++) {
	// value = bitmap [rowIndex];
	// This is replaced by the three lines that appear next.
	for (j = 0; j < size + 1; j++) {
	    if (j < biWidth) {
		value	= bitmap[rowIndex + 1];
	    } else {
		value	= bitmap[rowIndex];
	    }

	    rgb[0]	= (byte) (value & 0xFF);
	    rgb[1]	= (byte) ((value >> 8) & 0xFF);
	    rgb[2]	= (byte) ((value >> 16) & 0xFF);
	    fo.write(rgb);

	    if (rowCount == biWidth) {
		padCount	+= pad;

		for (i = 1; i <= pad; i++) {
		    fo.write(0x00);
		}

		rowCount	= 1;
		rowIndex	= lastRowIndex - biWidth;
		lastRowIndex	= rowIndex;
	    } else {
		rowCount++;
	    }

	    rowIndex++;
	}

	// --- Update the size of the file
	bfSize		+= padCount - pad;
	biSizeImage	+= padCount - pad;

	/*
	 * }
	 * catch (Exception wb) {
	 *   wb.printStackTrace ();
	 * }
	 */
    }

    /*
     * writeBitmapFileHeader writes the bitmap file header to the file.
     *
     */

    /**
     * Method description
     *
     *
     * @throws Exception
     */
    private void writeBitmapFileHeader() throws Exception {
	fo.write(bfType);
	fo.write(intToDWord(bfSize));
	fo.write(intToWord(bfReserved1));
	fo.write(intToWord(bfReserved2));
	fo.write(intToDWord(bfOffBits));
    }

    /*
     *
     * writeBitmapInfoHeader writes the bitmap information header
     * to the file.
     *
     */

    /**
     * Method description
     *
     *
     * @throws Exception
     */
    private void writeBitmapInfoHeader() throws Exception {
	fo.write(intToDWord(biSize));
	fo.write(intToDWord(biWidth));
	fo.write(intToDWord(biHeight));
	fo.write(intToWord(biPlanes));
	fo.write(intToWord(biBitCount));
	fo.write(intToDWord(biCompression));
	fo.write(intToDWord(biSizeImage));
	fo.write(intToDWord(biXPelsPerMeter));
	fo.write(intToDWord(biYPelsPerMeter));
	fo.write(intToDWord(biClrUsed));
	fo.write(intToDWord(biClrImportant));
    }

    /*
     *
     * intToWord converts an int to a word, where the return
     * value is stored in a 2-byte array.
     *
     */

    /**
     * Method description
     *
     *
     * @param parValue
     *
     * @return
     */
    private byte[] intToWord(int parValue) {
	byte	retValue[]	= new byte[2];

	retValue[0]	= (byte) (parValue & 0x00FF);
	retValue[1]	= (byte) ((parValue >> 8) & 0x00FF);

	return (retValue);
    }

    /*
     *
     * intToDWord converts an int to a double word, where the return
     * value is stored in a 4-byte array.
     *
     */

    /**
     * Method description
     *
     *
     * @param parValue
     *
     * @return
     */
    private byte[] intToDWord(int parValue) {
	byte	retValue[]	= new byte[4];

	retValue[0]	= (byte) (parValue & 0x00FF);
	retValue[1]	= (byte) ((parValue >> 8) & 0x000000FF);
	retValue[2]	= (byte) ((parValue >> 16) & 0x000000FF);
	retValue[3]	= (byte) ((parValue >> 24) & 0x000000FF);

	return (retValue);
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
